import re
import logging
import copy
import hashlib
import uuid
import base64
import decorator
import time
import os
import random
import datetime
from google.appengine.ext import db
from google.appengine.ext.db import GqlQuery
import inspect
from wtforms.form import Form
from wtforms import fields
from settings import Settings
from yengine import db as ydb, get_queue_target
from wtforms import validators
from babel.numbers import parse_decimal
from google.appengine.api import memcache
from google.appengine.api import search
import functools
import pytz
from pytz import timezone, utc
from requirers import *
from yengine.db import EntityProblem
import itertools
from google.appengine.ext import deferred


class staticinstancemethod(object):
  """
  Credit: http://stackoverflow.com/questions/5892619/static-and-instance-methods-in-python
  """
  def __init__(self, func):
    self.func = func

  def __get__(self, instance, owner):
    return functools.partial(self.func, instance)

class classinstancemethod(object):
    """
    Credit: https://bitbucket.org/ianb/formencode/
    Acts like a class method when called from a class, like an
    instance method when called by an instance.  The method should
    take two arguments, 'self' and 'cls'; one of these will be None
    depending on how the method was called.
    """

    def __init__(self, func):
        self.func = func

    def __get__(self, obj, type=None):
        return _methodwrapper(self.func, obj=obj, type=type)

class _methodwrapper(object):
    """
    Credit: https://bitbucket.org/ianb/formencode/
    """
    def __init__(self, func, obj, type):
        self.func = func
        self.obj = obj
        self.type = type

    def __call__(self, *args, **kw):
        assert not kw.has_key('self') and not kw.has_key('cls'), (
            "You cannot use 'self' or 'cls' arguments to a "
            "classinstancemethod")
        return self.func(*((self.obj, self.type) + args), **kw)

    def __repr__(self):
        if self.obj is None:
            return ('<bound class method %s.%s>'
                    % (self.type.__name__, self.func.func_name))
        else:
            return ('<bound method %s.%s of %r>'
                    % (self.type.__name__, self.func.func_name, self.obj))

def rchop(thestring, ending):
  '''
    remove part of a string but only if
    the part to remove occurs at the end
  '''
  if thestring.endswith(ending):
    return thestring[:-len(ending)]
  return thestring

def lchop(thestring, beginning):
  '''
    remove part of a string but only if
    the part to remove occurs at the beginning
  '''
  if thestring.startswith(beginning):
    return thestring[len(beginning):]
  return thestring


class AutoFloatProperty(db.FloatProperty):

  def __set__(self, model_instance, value):
    value = self.parse_value(value)
    db.FloatProperty.__set__(self, model_instance, value)

  @staticmethod
  def parse_value(value):
    if value and not isinstance(value, float):
      turn_negative = False
      if isinstance(value, basestring):
        number = re.sub('[^+\\-0-9,.]+', '', value)
        if number and value.startswith('-') :
          turn_negative = True
        logging.debug('parsed number = ' + unicode(number))
        value = parse_decimal(number, locale='en_GB')
      value = float(value)
      if value > 0 and turn_negative:
        value *= -1
    return value


class AutoIntegerProperty(db.IntegerProperty):

  def __set__(self, model_instance, value):
    if value and value.__class__ != int:
      try:
        value = int(value)
      except ValueError:
        value = int(float(value))
    db.IntegerProperty.__set__(self,model_instance,value)


class EmailProperty(db.EmailProperty):
  def validate(self, value):
    if not value:
      if self.required:
        raise BadValueError('Property %s is required' % self.name)
      return None
    if not re.match(r'[^@]+@[^@]+\.[^@]+', value):
      raise db.BadValueError('Property %s must be a valid email address' % self.name)
    if self.validator is not None:
      self.validator(value)
    return value

class UniquePropertyModel(ydb.Model):

  unique_properties = []
  unique_scope = None


  def __init__(self,*args,**kwargs):
    db.Model.__init__(self,*args,**kwargs)
    if "_from_entity" in kwargs:
      if kwargs["_from_entity"]:
        return
    for property in self.unique_properties:
      if property in kwargs and kwargs[property]:
        query = self.__class__.all(keys_only=True).filter('%s =' % property, kwargs[property])
        if 'parent' in kwargs:
          query.ancestor(kwargs['parent'])
        stored_key = query.get()
        if stored_key:
          if self.unique_scope:
            data_object = db.get(stored_key)
            if self.unique_scope in kwargs and hasattr(data_object,self.unique_scope):
              if data_object.__getattribute__(self.unique_scope) != kwargs[self.unique_scope]:
                logging.info("%s value %s is being used but notwithin the given scope!" % (property,kwargs[property]))
                return
          logging.info("%s is already being used with key %s, please choose another" % (kwargs[property],str(stored_key)))
          raise db.BadValueError("%s is already being used" % kwargs[property])

  def __setattr__(self,name,value,*args,**kwargs):
    if name in self.unique_properties and value:
      query = GqlQuery( "Select __key__ from %s where %s = :1" % (self.__class__.__name__, name),value)
      check_key = query.get()
      try:
        model_key = self.key()
      except db.NotSavedError:
        model_key = None
      if check_key and model_key:
        if not check_key == model_key:
          if self.unique_scope and self.unique_scope in kwargs:
            check_model = db.get(check_key)
            if self.__getattr__(unique_scope) == check_model.__getattr__(unique_scope):
              raise db.BadValueError("%s is already being used" % value)
          else:
            raise db.BadValueError("%s is already being used" % value)
    db.Model.__setattr__(self,name,value)


class EmailThrottlingCountClass(db.Model):
    added_on = db.DateTimeProperty(auto_now_add=True, indexed=True)
    email_address = db.StringProperty(indexed=True, required=True)


class Wombat(UniquePropertyModel):

  #all wombat properties must have a scope
  #this is to allow sensible default permissioning
  #no scope means it's a global object
  #can be searched with scope = Model.Key()
  #this essentially matches the account property

  @staticmethod
  def yRKey(key_string):
    try:
      entity_key = db.Key(str(key_string))
      key_path = entity_key.to_path()
      logging.debug('Found key path' + str(key_path))
      entity_key = db.Key.from_path(*key_path)
      logging.debug('entity key = ' + str(entity_key))
      return entity_key
    except db.BadKeyError, e:
      raise
    except Exception as e:
      logging.exception(e)
      return None

  lower = ['email_address', 'email']
  strong_exclude = ['scope'] # to be hidden from everyone
  exclude = ['scope', 'lower'] # to be hidden from regular users only
  scope = db.ListProperty(db.Key,default=None)
  hidden = db.BooleanProperty(default=False)
  internal_notes = db.TextProperty()
  tags = db.StringListProperty(verbose_name='Tags<br /><small>new line-separated</small>')
  added_on = db.DateTimeProperty(auto_now_add=True)
  modified_on = db.DateTimeProperty(auto_now=True)
  PROPERTY_CACHE = {}
  FIELDS_VISIBILITY = {}

  @classmethod
  def get_descendant_classes(cls):
    """Credit: http://stackoverflow.com/a/5883218/784331"""
    subclasses = set()
    work = [cls]
    while work:
      parent = work.pop()
      for child in parent.__subclasses__():
        if child not in subclasses:
          subclasses.add(child)
          work.append(child)
    return subclasses

  @property
  def str(self):
    return unicode(self)

  def __str__(self):
    string = self.get_title('')
    if not string:
      for prop in self._properties:
        #setting the name will return this instead of the ugly string
        if prop == 'name' and isinstance(self.__class__.name, db.StringProperty) and getattr(self, prop):
          string =  str(getattr(self, prop))
          break
        if 'email' in prop and isinstance(getattr(self.__class__, prop), db.EmailProperty) and getattr(self, prop):
          string =  str(getattr(self, prop))
          break
    if not string:
      string = self.get_user_friendly_class_name() + ' #' + self.key_str()
    return string

  def get_title(self, default = None):
    title = default
    if hasattr(self, 'TITLE') and self.TITLE and hasattr(self, self.TITLE):
      title = getattr(self, self.TITLE)
    if not title and default is None:
      title = str(self)
    return title

  @property
  def title(self):
    return self.get_title()

  @property
  def header(self):
    return None

  def __setattr__(self,name,value,*args,**kwargs):
    if name in self.lower and value:
      value = value.lower()
    super(Wombat,self).__setattr__(name,value,*args,**kwargs)

  def __init__(self,*args,**kwargs):
    for key in self.lower:
      if key in kwargs and kwargs[key]:
        kwargs[key] = kwargs[key].lower()
    super(Wombat,self).__init__(*args,**kwargs)

    self._actions = {}
    if self.__class__.scope:
      from plugins import *
      self.register_action(ScopeManager, 'scope', hidden=True)
      self.register_action(PropertyNullifier, 'nullify_property', hidden=True)
      self.register_action(JSONViewer, 'as_json', hidden=True)


  def put(self, *args, **kwargs):
    if isinstance(getattr(self.__class__, 'scope'), db.Property):
      self.refresh_scope(False)
    if (self.__class__.__name__ != 'Account' or self.is_saved()) and not db.is_in_transaction():
      from config import WombatConfig
      if hasattr(self, 'has_default_scope_only'):
        self.has_default_scope_only = self.has_scope(WombatConfig.default_scope) and len(self.get_scope()) == 1
      self.add_scope(WombatConfig.default_scope)
    put_result = super(Wombat, self).put(*args, **kwargs)
    try:
      pass#self.make_search_index()
    except Exception as e:
      logging.exception(e)
      #raise
    return put_result

  def get_clone(self, **extra_properties):
    properties = {}
    logging.info('about to clone an entity')
    for key, property in self.properties().iteritems():
      logging.info(key)
      value = getattr(self, key)
      if key == 'scope':
        properties['scope'] = []
      elif isinstance(property, (db.DateProperty, db.TimeProperty, db.DateTimeProperty)) and True in [property.auto_now_add, property.auto_now]:
        continue
      elif type(property) in [db.ReferenceProperty, db.SelfReferenceProperty]:
        continue
      elif key == 'hidden':
        continue
      elif hasattr(self, 'unique_properties') and key in self.unique_properties:
        continue
      else:
        properties[key] = copy.deepcopy(value)
    properties.update(extra_properties)
    logging.debug(properties)
    return self.__class__(**properties)

  def get_property(self, path):
    element = self
    path_elements = path.split('.')
    path_length = len(path_elements)
    i = 0
    while i < path_length:
      name = path_elements[i]
      if i+1 < path_length and hasattr(element, 'get_cached_reference_property') and hasattr(element.__class__, name) and isinstance(getattr(element.__class__, name), db.ReferenceProperty):
        attr = element.get_cached_reference_property(name, path_elements[i+1])
        logging.debug('element.get_cached_reference_property(%s, %s) == %s' % (name, path_elements[i+1], attr))
        i += 1
      else:
        attr = getattr(element, name)
      if attr == None:
        return None
      else:
        element = attr
      i += 1
    return element

  @property
  def __key__(self):
    return self.key()

  def insert_into_MySQL(self):
    pass

  def get_cache_path(self, property = None):
    path = '/datastore/%s' % self.key()
    if property:
      path += '/' + property
    return path

  @property
  def cache_path(self):
    return self.get_cache_path(None)

  def __getitem__(self, key):
    if not isinstance(key, basestring):
      raise IndexError('%s is not a valid item key' % str(key))
    return self.get_property(key)

  def update_properties(self, properties, value_filter = lambda value: bool(value)):
    """Update a sequence of properties. Ignore the values that do not match the given filter.
    If no custom filter is specified, only set values that evaluate to True.
    >>> entity.update_properties({'property_name': 'new_property_value', 'another_property_name': 'new property_value'})
    >>> entity.property_name
    new_property_value
    """
    properties = ((property_name, new_value) for property_name, new_value in properties.iteritems() if value_filter(new_value))
    for property_name, new_value in properties:
      setattr(self, property_name, new_value)


  def queue_search_index(self):
    self.queue_method_calls('make_search_index')

  def make_search_index(self):
    doc_id = '/datastore/'+str(self.key())
    index = search.Index(name='/wombat')

    if self.is_hidden():
      logging.debug('deleting search index for %s [%s]' %(self, self.key()))
      index.delete([doc_id])
      return

    logging.debug('creating search index for %s [%s]' %(self, self.key()))
    fields = [
      search.TextField(name='self', value='%s %s' % (self.key(), self.get_title())),
      search.AtomField(name='key', value=str(self.key())),
      search.AtomField(name='id', value=self.key_str()),
      search.TextField(name='title', value=self.get_title()),
      search.TextField(name='string', value=str(self)),
      search.TextField(name='kind', value=self.get_user_friendly_class_name()),
      search.TextField(name='plural_kind', value=self.get_user_friendly_plural_class_name()),
      search.TextField(name='class', value=self.__class__.__name__),
      search.TextField(name='path', value=self.get_uri()),
      search.TextField(name='scope', value=' '.join(map(lambda a: str(a), self.get_scope()))),
      search.TextField(name='ancestor', value=' '.join(map(lambda a: '%s' % (a.key()), self.get_main_ancestors()))),
      #search.TextField(name='parent', value=' '.join(map(lambda a: '%s %s' % (a.key(), a.get_title()), self.get_parents()))),
      #search.TextField(name='child', value=' '.join(map(lambda a: '%s %s' % (a.key(), a.get_title()), self.get_children()))),
    ]
    for tag in self.tags:
      fields.append(search.TextField(name='tag', value=tag))

    parent = self.get_main_parent()
    if parent:
      fields.append(search.TextField(name='parent', value='%s %s' % (parent.key(), parent.get_title())))

    if hasattr(self, 'added_on') and self.added_on and hasattr(self.added_on, 'date'):
      fields.append(search.DateField(name='date', value=self.added_on.date()))

    fields.extend(self.get_extra_search_index_fields())
    document = search.Document(doc_id=doc_id, fields=fields)
    index.put(document)

  def get_extra_search_index_fields(self):
    return []

  @property
  def extra_tabs(self):
    return []

  def refresh_scope(self, force=True):
    from config import WombatConfig
    if not self.get_scope() or force:
      if self.is_saved():
        logging.debug('Refreshing scope for ' + str(self) + ' [' + str(self.key()) + ']')
      else:
        logging.debug('Refreshing scope')
      main_parent = self.get_main_parent()
      if main_parent:
        if not main_parent.get_scope(True):
          main_parent.refresh_scope()
          main_parent.put()
        self.replace_scope(main_parent.get_scope(False, False))
      if self.get_scope(True, False):
        logging.debug('The entity has now got scope')
        for parent in self.get_parents(True).values():
          logging.debug('Parent: ' + repr(parent))
          parent.refresh_scope()
          parent.put()
    self.add_scope(WombatConfig.default_scope)


#init will automatically add scope depending on who is logged in..

#  def __init__(self,*args,**kwargs):
#    parent_has_been_set = False
#    if '_from_entity' in kwargs and kwargs['_from_entity'] == True:
#      #logging.info('from entity so not adding parent')
#      #check for retrospective parent add..
#      pass
#    elif not 'parent' in kwargs:
#    #if not self.parent():
#      #find out if there is only one parent..
#      parent_count = 0
#      for p in kwargs:
#        prop_val = kwargs[p]
#        if isinstance(prop_val,db.Model) or isinstance(prop_val,db.Key):
#          parent_count += 1
#          parent_model = prop_val
#      if parent_count == 1: # only want to autoset if there is only one parent
#        #auto set the parent
#        logging.info("Adding auto parent for " + str(parent_model.__class__))
#        #self.parent = parent_model
#        kwargs['parent'] = parent_model
#        parent_has_been_set = True
#    else:
#      logging.info('Parent was in kwargs so not overridigin')
#    super(Wombat,self).__init__(*args,**kwargs)
#    #logging.info("initialised object " + self.__class__.__name__)
#    #retrospectively add parent if required
#    if '_from_entity' in kwargs and kwargs['_from_entity'] == True:
#      parent_count = 0
#      parent_model = None
#      if not parent_has_been_set:
#        if not self._parent:
#          for p in self._properties:
#            try:
#              prop_val = getattr(self, p)
#              if isinstance(prop_val,Wombat):
#                parent_count += 1
#                parent_model = prop_val
#            except Exception as e:
#              logging.exception("while initialisng parent " +str(e))
#          if parent_count == 1 and parent_model:
#                logging.info(str(self) + " is setting parent retorspectively " + str(parent_model))
#                self._parent = parent_model

#            else:
#              logging.info("found parent model that wasn't saved.. not setting")
#          else:
#            logging.info("avoided a parental circular reference.")
#      else:
#        logging.info("self parent was already initialised.")
#    else:
#      logging.info("self.parent was not none so won't add parent")

  def __getattribute__(self, name):
    #logging.info('[wombat] attribute name = ' + name)
    try:
      return object.__getattribute__(self,name)
    except AttributeError:
      raise
    except Exception as e:
      if 'ReferenceProperty failed to be resolved' in str(e):
        logging.info("Bad reference found, setting to null" + str(e))
        setattr(self, name, None)
        return None
      #otherwise it's a legit error and we want to raise ti so
      #python can try the super class, or getattr methods
      else:
        raise



  @staticmethod
  def props(obj):
    pr = {}
    for name in dir(obj):
      try:
        if not name.startswith('_'):
          value = getattr(obj, name)
          if not inspect.ismethod(value):
            pr[name] = value
      except Exception:
        pass
    return pr

  #use class method so it points to the subclass
  @classmethod
  def class_url(cls):
    s = "/" + rchop(cls.__module__,'models')
    s += Wombat.class_name_to_path_name(cls.__name__)
    s = s.replace('.','/')
    return s

  @staticmethod
  def class_name_to_path_name(class_name):
    '''
      ReceiptFeedback ==> receipt_feedback
    '''
    name_parts = re.findall('[A-Z][^A-Z]*', class_name)
    instance_name = "_".join([word[0].lower() + word[1:] for word in name_parts])
    return instance_name

  @classmethod
  def get_path_name(cls):
    return cls.class_name_to_path_name(cls.__name__)

  def _form_post_string(self):
    tmp_props = self._form_properties()
    props = copy.deepcopy(tmp_props)
    for key in tmp_props:
      if 'password' in key:
        props['confirm_'+key] = props[key]
    s = ""
    for key in props:
      if hasattr(self.__class__, 'strong_exclude') and key in self.__class__.strong_exclude:
        continue
      if not s == "":
        s += "&"
#      if key.endswith('image'):

      if hasattr(self.__class__, key) and type(getattr(self.__class__, key)) == db.ReferenceProperty and not getattr(self, key):
        s += key+"=__None"
      elif hasattr(self.__class__, key) and getattr(self.__class__, key).__class__.__name__ in ['EmailProperty', 'StringProperty'] and not getattr(self, key):
        s += key+"="
      elif hasattr(self.__class__, key) and type(getattr(self.__class__, key)) == db.DateTimeProperty:
        s += key+"="+str(props[key])[0:str(props[key]).find('.')]
      elif hasattr(self.__class__, key) and type(getattr(self.__class__, key)) == db.GeoPtProperty:
        continue
      else:
        s += key+"="+str(props[key])
    logging.info('Form post string = ' + s)
    return s

  def _form_properties(self):
    '''
      returns all the properties of the object but as strings and importantly
      converts a db.Model reference into the key equivalent
    '''
    return_str_props = {}
    for prop in self._properties:
      cur_prop_val = getattr(self, prop)
      if isinstance(cur_prop_val,db.Model):
        s = str(cur_prop_val.key())
      else:
        s = str(cur_prop_val)
        if s == "True":
          s = "checked"
        if s == "False":
          s = ""
      return_str_props[prop]= s
    return return_str_props

  def href(self):
    '''
      returns the url of this object i.e /module_name/class_name?key=__key__
    '''
    s = self.class_url()
    s += '?key=' + str(self.key())
    return s

  @staticmethod
  def get_parent(entity):
    '''
      if the entity has only one reference property then return it (if it has a value)
    '''
    properties = Wombat.props(entity)
    for p in properties:
      prop_val = getattr(entity, p)
      if isinstance(prop_val,db.Model):
        logging.debug(' found parent ' + str(type(prop_val)) + " with val " + str(prop_val))
        return prop_val
    return None


#  @staticmethod
#  def get_root_node(entity):
#    '''
#      returns the entity's root node
#    '''
#    parent = Wombat.get_parent(entity)
#    if parent == None:
#      return entity
#    return Wombat.get_root_node(parent)

  @staticmethod
  def  get_root_node(entity):
    '''
      returns the final parent ancestor of the given entity
    '''
    if entity:
      parent_ent = entity.parent()
      if parent_ent == None:
        return entity
      return Wombat.get_root_node(parent_ent)
    else:
      return None


  @staticmethod
  def are_related(object, possible_sibling):
    '''
      if parentobject is parent of object1 and object2 then
      are_related(object1,object2) ==> true
    '''
    object_root_node = Wombat.get_root_node(object)
    compare_root_node = Wombat.get_root_node(possible_sibling)
    if object_root_node == compare_root_node:
      return True
    else:
      return False

  def get_parents(self, exclude_main = False):
    parents = {}
    properties = self.properties()
    for property_name in properties.keys():
      if isinstance(properties[property_name], db.ReferenceProperty) and isinstance(getattr(self, property_name), Wombat):
        parent = getattr(self, property_name)
        if parent.is_hidden():
          continue
        if exclude_main:
          main = self.get_main_parent()
          if main and main.key() == parent.key():
            continue
        parents[property_name] = parent
    return parents

  def get_children(self, with_deleted = False, main_only = False):
    config = db.create_config(deadline=5, read_policy=db.EVENTUAL_CONSISTENCY)
    children = {}
    attributes = self.props(self)
    for attribute_name in attributes:
      if attribute_name[-4:] == '_set':
        model = getattr(self, attribute_name)._model_class
        class_path = str(model.__module__) + '.' + model.__name__
        if not main_only or hasattr(self.__class__, 'MAIN_CHILDREN_CLASSES') and class_path in self.__class__.MAIN_CHILDREN_CLASSES:
          for r in getattr(self, attribute_name).filter('hidden', False).run(config=config):
            try:
              children[r.__class__].append(r)
            except KeyError:
              children[r.__class__] = [r]
    return children

  def get_possible_children(self):
    children = []
    attributes = self.props(self)
    for attribute_name in attributes:
      if attribute_name[-4:] == '_set':
        set = getattr(self, attribute_name)
        model = set._model_class
        if issubclass(model, Wombat):
          children.append(model)
    return children

  def count_possible_parents(self):
    count = 0
    properties = self.properties()
    for property_name in properties.keys():
      if isinstance(properties[property_name], db.ReferenceProperty):
          count += 1
    return count

  def get_main_parent(self):
    parents = self.get_parents()
    if hasattr(self, 'MAIN_PARENT'):
      if self.MAIN_PARENT == None:
        return None
      if self.MAIN_PARENT in parents:
        return parents[self.MAIN_PARENT]
    if self.count_possible_parents() == 1 and len(parents) == 1:
      return parents[parents.keys()[0]]
    return None

  def get_main_ancestors(self, with_self = False):
    ancestors = []
    ancestor = self
    while True:
      mp = ancestor.get_main_parent()
      if not mp or mp is None:
        break
      ancestors.insert(0, mp)
      ancestor = mp
    if with_self:
      ancestors.append(self)
    return ancestors

  def get_siblings(self, with_self = False):
    parent = self.get_main_parent()
    logging.debug(parent)
    if not parent:
      logging.debug('Could not get the main parent of ' + str(self))
      return None
    parents_children = parent.get_children()
    if not self.__class__ in parents_children:
      logging.info('Could not fetch children of ' + str(parent))
      return None
    siblings = []
    for sibling in parents_children[self.__class__]:
      if (sibling.key() != self.key() or with_self) and not sibling.is_hidden():
        siblings.append(sibling)
    return siblings

  def get_main_children_class(self):
    if not hasattr(self, 'MAIN_CHILDREN_CLASSES') or not isinstance(self.MAIN_CHILDREN_CLASSES, (list, tuple)) or len(self.MAIN_CHILDREN_CLASSES) == 0:
      return None
    from wombat import _get_func # causes a conflict when at the beginning of the file
    children = self.get_children()
    for path in self.MAIN_CHILDREN_CLASSES:
      theclass = _get_func(path)
      if theclass in children:
        return theclass
    return None

  def get_entities_to_hide(self):
    """This needs to work both ways, i.e. return a list of entities to reveal when called on a visible an entity, and a list of other entities to hide when called on a hidden entity."""
    return []

  def register_action(self, classobj, name = None, verbose_name = None, hidden = False):
    from wombat import WombatException

    if not hasattr(classobj, 'get') and not hasattr(classobj, 'post'):
      raise WombatException(str(classobj) + ' is not a valid Wombat action plugin')

    methods = []
    if hasattr(classobj, 'get'):
      methods.append('get')
    if hasattr(classobj, 'post'):
      methods.append('post')

    if not name or not re.search('[a-zA-Z]', name):
      name = classobj.__name__
    name = re.sub('(?!^)([A-Z])', '_\\1', name).strip().lower()
    name = re.sub('^[^a-z]+|[^a-z]+$', '', name)
    name = re.sub('[^a-zA-Z]+', '_', name).strip().lower()

    if not verbose_name:
      verbose_name = name.replace('_', ' ')
      verbose_name = verbose_name.strip().title()
      verbose_name = re.sub('\s+', ' ', verbose_name)

    self._actions[name] = {'name': name, 'verbose_name': verbose_name, 'class': classobj, 'methods': methods, 'hidden': hidden}

  def get_action(self, name):
    return self.get_actions()[name]

  def get_actions(self):
    return self._actions

  @classmethod
  def get_user_friendly_class_name(cls):
    class_name = cls.__name__
    class_name = re.sub('([A-Z])', ' \\1', class_name).strip()
    return class_name

  @classinstancemethod
  def get_user_friendly_plural_class_name(self=None, cls=None):
    if self:
      return self.get_user_friendly_class_name()+'s'
    return cls.get_user_friendly_class_name()+'s'

  def get_user_friendly_key_name(self):
    '''
      probably rubbish
    '''
    if self.key().name():
      return self.key().name()
    return ''

  @classmethod
  def get_uri_base(cls):
    uri = ''
    parts = cls.__module__.split('.')
    parts.append(cls.__name__)

    for part in parts:
      if part != 'models':
        uri += '/' + re.sub('(?!^)([A-Z])', '_\\1', part).strip().lower()

    return uri

  @property
  def uri(self):
    return self.get_uri()

  def get_uri(self):
    uri = self.get_uri_base()
    uri += '?key=' + str(self.key())
    return uri

  def get_cached_reference_property(self, reference_property, property_name):
    '''
    Initial idea ;)
    key = self.key()
    model = self.__class__
    property_chain = property_path.split('.')
    for property_name in property_chain:
      attribute = getattr(model, property_name)
      if isinstance(attribute, db.ReferenceProperty):
        key = get_entity_reference_property_key(db.get(key), property_name)
        model = attribute.reference_class'''
    value = None
    model = self.__class__
    attribute = getattr(model, reference_property)
    key = None
    if hasattr(model, reference_property + '_key'):
      key = getattr(self, reference_property + '_key')
      if not key:
        return None

    if not key and hasattr(self, reference_property):
      try:
        key = self.get_reference_property_key(reference_property)
      except Exception as e:
        logging.exception(e)
      if not key:
        if attribute.default:
          return attribute.default

    if not key:
      return None
    cache_path = '/datastore/'+str(key)+'/'+property_name
    #if cache_path in attribute.reference_class.PROPERTY_CACHE:
    #  value = attribute.reference_class.PROPERTY_CACHE[cache_path]
    #else:
    #  value = memcache.get(cache_path)
    #  if value:
    #    attribute.reference_class.PROPERTY_CACHE[cache_path] = value
    if not value:
      referenced_entity = None
      try:
        referenced_entity = db.get(key)
      except db.BadKeyError:
        pass
      except:
        EntityProblem.add(key, 'Could not load entity to get the %s property' % property_name)
      if referenced_entity:
        value = getattr(referenced_entity, property_name)
        if not isinstance(value, db.Model):
          memcache.set(cache_path, value)
        #attribute.reference_class.PROPERTY_CACHE[cache_path] = value
      else:
        logging.info('key %s could not be resolved to an entity' % key)
    #else:
    #  logging.info('loading from memcache - ' + cache_path)
    return value

  def get_hooks(self):
    hooks = []
    for scope in self.get_scope(False, False):
      scope_object = db.GqlQuery('SELECT * FROM %s WHERE __key__ = :1' % scope.kind(), scope).get()
      if scope_object and scope_object.entity_hooks:
        hooks += scope_object.entity_hooks
    return hooks

  def queue_hooks(self, hook_method):
    logging.info('About to queue %s hooks for %s' % (hook_method, repr(self)))
    logging.info('Scope = %s' % self.get_scope())
    logging.info('Hooks = %s' % self.get_hooks())
    from wombat import _get_class
    hooks = self.get_hooks()
    for hook in hooks:
      hook_class = _get_class(hook)
      if not hook_class:
        logging.warn('%s could not be resolved to a class' % hook)
        continue
      if hook_class.handles_model(self.__class__):
        deferred.defer(hook_class.call, self.key(), hook_method, _queue='wombat', _target=get_queue_target('request'))
        logging.info('Enqueued hook %s.%s for %s' % (hook, hook_method, repr(self)))
        memcache.set('last_entity_hook_enqueued', '%s.%s' % (hook, hook_method))

  def get_scope(self, exclude_default=False, exclude_service_accounts=True):
    #if hasattr(self, '_account') and self._account:
    #  scope = self.scope[:]
    #  scope.append(self._account.key())
    #  return scope
    if not self.scope:
      return []
    scope = self.scope[:]
    if exclude_default:
      from config import WombatConfig
      default_scope = WombatConfig.default_scope
      if self.has_scope(default_scope):
        scope.remove(default_scope.key())
    if exclude_service_accounts:
      new_scope = []
      for s in scope:
        so = db.GqlQuery('SELECT service_account FROM %s WHERE __key__ = :1' % s.kind(), s).get()
        if not so or not so.service_account:
          new_scope.append(s)
      scope = new_scope
    return scope

  def add_scope(self, item):
    if not isinstance(item, db.Key):
      item = item.key()
    if not self.scope is None and not item in self.scope:
      self.scope.append(item)

  def remove_scope(self, item):
    if not isinstance(item, db.Key):
      item = item.key()
    self.scope.remove(item)

  def has_scope(self, item):
    if self.scope is None:
      return False
    if not isinstance(item, db.Key):
      item = item.key()
    return item in self.scope

  def clean_scope(self):
    for key in self.get_scope(False, False):
      entity = None
      try:
        entity = db.get(key)
      except db.BadKeyError:
        pass
      if not entity or entity.is_hidden():
        self.remove_scope(key)

  def clear_scope(self):
    self.scope = []

  def extend_scope(self, items):
    if not items:
      return
    for item in items:
      self.add_scope(item)

  def replace_scope(self, keys):
    logging.info(self.scope)
    self.clear_scope()
    logging.info(self.scope)
    self.extend_scope(keys)
    logging.info(self.scope)

  def get_mutual_scope(self, *args):
    scope = set(self.get_scope())
    for item in args:
      scope &= set(item.get_scope())
    return list(scope)

  def hide(self):
    self.hidden = True
    self.put()

  def reveal(self):
    self.hidden = False
    self.put()

  def is_hidden(self):
    return self.hidden

  def key_str(self):
    if not self.is_saved():
      return ''
    if self.key().name():
      return str(self.key().name())
    return str(self.key().id())

  @property
  def key_id_or_name(self):
    return self.key().id_or_name()

  @classmethod
  def get_list_projection(cls, include_hidden=False):
    if hasattr(cls, 'LIST_PROJECTION') and cls.LIST_PROJECTION:
      projection = cls.LIST_PROJECTION[:]
      if include_hidden:
        projection.append('hidden')
      if hasattr(cls, 'external_id'):
        projection.append('external_id')
      logging.debug('Projection = ' + str(projection))
      return projection
    return None

  @classmethod
  def all(cls, *args, **kwargs):
    query = WombatQuery(cls, *args, **kwargs)
    if issubclass(cls, db.polymodel.PolyModel) and cls != cls.__root_class__:
      query.filter(db.polymodel._CLASS_KEY_PROPERTY, cls.class_name())
    return query


  @staticmethod
  def as_scope(item):
    if isinstance(item, db.Key):
      return item
    return item.key()

  @classmethod
  def get_default_entities(cls, account_key_name = 'defaults'):
    from config import WombatConfig
    title = account_key_name.replace('_', ' ').title()
    account = Account.get_or_insert(key_name=account_key_name, name=title, scope=[WombatConfig.default_scope.key()])
    return cls.all().filter('hidden !=', True).scope(account)

  @classmethod
  def get_reference_queries(cls, referencing_class):
    return [cls.all(_list_projection=True).filter('hidden', False)]


class WombatQuery(db.Query):

  def __init__(self, model, *args, **kwargs):
    if '_list_projection' in kwargs:
      if kwargs['_list_projection']:
        kwargs['projection'] = model.get_list_projection()
      del kwargs['_list_projection']
    super(WombatQuery, self).__init__(model, *args, **kwargs)

  def scope(self, scope):
    self.filter('scope =', Wombat.as_scope(scope))
    return self

  def fetch_by_scope(self, scope_list, limit=1000):
    final_result = []
    for scope in scope_list:
      logging.debug('fetching for scope' + str(scope))
      if limit <= 0:
        logging.debug('limit exceeded')
        break
      query = copy.deepcopy(self)
      query.scope(scope)
      result = query.fetch(limit)
      limit -= len(result)
      final_result.extend(result)
    return final_result


class ServiceProvider(Wombat):
  name = db.StringProperty(default='yReceipts', required=True)
  email_user = db.StringProperty(default=None, required=False)
  email_address = db.StringProperty(default='receipts@yreceipts.com', required=True)
  domain = db.StringProperty(default=None)
  url = db.StringProperty(verbose_name='Company Website (include http://)')
  signature = db.StringProperty(default='The yReceipts Team', required=True)
  use_amazon_mailer = db.BooleanProperty(default=True)
  #default_receipt_layout_keys = db.StringListProperty(default=[])
  #default_receipt_email_layout_keys = db.StringListProperty(default=[])
  reports_email_addresses = db.StringListProperty(indexed=False, verbose_name="Reports Email Addresses<br /><small>(newline-separated)</small>")
  send_weekly_reports = db.BooleanProperty(indexed=True, default=False)
  include_reporting_links = db.BooleanProperty(indexed=True, default=True)
  weekly_report_transaction_fields = db.StringListProperty(default=['store_name,Store', 'transaction_number,Transaction Number', 'transaction_date,Date', 'transaction_time,Time', 'associate_name,Associate Name', 'consumer_email_address,Email Address', 'consumer_subscribed,Subscribed', 'store_performance,Store Performance', 'store_feedback,Feedback'])
  credit = db.TextProperty(default='''<br />Paperless receipts provided by <a href="http://www.yreceipts.com/?utm_source=yReceipt&amp;utm_medium=email&amp;utm_content=yReceipt_banner_logo&amp;utm_campaign=yreceipt_provided_by_yReceipts" target="_blank">yReceipts</a>''')
  aws_access_key_id = db.StringProperty()
  aws_secret_access_key = db.StringProperty()
  aws_region = db.StringProperty()

  LIST_PROJECTION = ['name']
  FIELDS_VISIBILITY = {
    'manager': ['signature', 'reports_email_addresses', 'send_weekly_reports']
  }
  FIELDS_ORDER = [
    'name',
    'url',
    (
      'White-label',
      [
        'email_user',
        'domain',
        'signature',
        'credit'
      ]
    ),
    (
      'Reports',
      [
        'send_weekly_reports',
        'reports_email_addresses',
        'weekly_report_transaction_fields',
        'include_reporting_links',
      ]
    ),
    (
      'Amazon SES',
      [
        'use_amazon_mailer',
        'email_address',
        'aws_access_key_id',
        'aws_secret_access_key',
        'aws_region',
      ]
    )
  ]

  def get_extra_search_index_fields(self):
    fields = []
    if self.email_user:
      fields.append(search.TextField(name='email_user', value=str(self.email_user)))
    fields.append(search.AtomField(name='domain', value=str(self.domain)))
    fields.append(search.TextField(name='signature', value=str(self.signature)))
    return fields

  @classmethod
  def is_service_provider_key(cls, potential_key):
    providers = ServiceProvider.all(projection=['name'])
    for provider in providers:
      if potential_key.startswith(provider.name + '_') or potential_key.startswith(provider.name + '-'):
        return True
    return False

  @property
  def from_email_address(self):
    return self.get_from_email_address()

  def get_from_email_address(self, disable_appspotmail=False):
    if self.use_amazon_mailer:
      return self.email_address
    if self.email_user and not disable_appspotmail:
      return self.email_user + '@yreceipts.appspotmail.com'
    return 'customer.service@yreceipts.com'

  def refresh_scope(self, force=False):
    return

  def send_mail(self, to, subject, body, **kwargs):
    from wombat import send_mail
    kwargs['use_amazon'] = self.use_amazon_mailer
    if self.aws_access_key_id and self.aws_secret_access_key:
      kwargs['amazon_ses'] = {
        'access_key_id': self.aws_access_key_id,
        'secret_access_key': self.aws_secret_access_key,
        'region': self.aws_region,
      }
    if 'sender' in kwargs:
      sender = kwargs['sender']
      del kwargs['sender']
    else:
      sender = self.from_email_address
    return send_mail(sender, to, subject, body, **kwargs)

  @property
  def head_offices(self):
    from users.businesses.models import BusinessLocation
    from users.businesses.models import Company

    accounts = Account.all(keys_only=True).filter('service_provider = ', self)
    chain = itertools.chain(
      self.company_set.filter('is_ho =', True).filter('hidden =', False),
      self.businesslocation_set.filter('is_ho =', True).filter('hidden =', False),
      BusinessLocation.all().filter('is_ho = ', True).filter('hidden =', False).fetch_by_scope(accounts),
      Company.all().filter('is_ho = ', True).filter('hidden =', False).fetch_by_scope(accounts)
    )

    seen = set()
    return (ho for ho in chain if ho.key() not in seen and not seen.add(ho.key()))


def validate_datahub_index_name(name):
  if not name:
    return
  regex = r'^/datahub/[a-z0-9_]+$'
  if not re.match(regex, name):
    raise db.BadValueError('Datahub Index Name must match %s' % regex)


class Account(Wombat):
  name = db.StringProperty(required=True, verbose_name='Company Name')
  token = db.StringProperty()  # legacy
  _tokens = db.StringListProperty(verbose_name='Tokens<br /><small>new line-separated</small><br /><small>enter <i>+</i> to generate a random token</small>')
  service_account = db.BooleanProperty(default=False, verbose_name='Partner')
  service_provider = db.ReferenceProperty(ServiceProvider, default=None)
  external_id = db.StringProperty()
  entity_hooks = db.StringListProperty()
  send_internal_reports = db.BooleanProperty(default=False)
  allow_updating_from_documents = db.BooleanProperty(default=True)
  datahub_index_name = db.StringProperty(validator=validate_datahub_index_name)
  _status_token = db.StringProperty()

  MAIN_PARENT = None
  MAIN_CHILDREN_CLASSES = ['wombat.models.User']
  exclude = ['service_account']
  strong_exclude = ['token', '_status_token']
  LIST_PROJECTION = ['service_account', 'name']
  ORDER = ['service_account', 'name']
  FIELDS_VISIBILITY = {
    'guest': ['name'],
    'user': ['name'],
    'manager': ['name']
  }
  unique_properties = ['datahub_index_name']

  def refresh_scope(self, force=False):
    return

  @property
  def header(self):
    return self.service_account and 'Partners' or 'Retailers'

  @property
  def auth_key(self):
    string = '%s:%s' % (self.key().id_or_name(), self.tokens[0])
    return base64.b64encode(string)

  @property
  def auth_keys(self):
    auth_keys = []
    for token in self.tokens:
      string = '%s:%s' % (self.key().id_or_name(), token)
      auth_key = base64.b64encode(string)
      auth_keys.append(auth_key)
    return auth_keys

  @property
  def tokens(self):
    tokens = []
    for token in self._tokens:
      if ' ' in token:
        tokens.append(token[:token.find(' ')])
      else:
        tokens.append(token)
    if self.token and not self.token in tokens:
      self._tokens.append('%s (original token)' % self.token)
      tokens.append(self.token)
      self.token = None
    return tokens

  def put(self, *args, **kwargs):
    self.refresh_tokens()
    if self.is_saved():
      from config import WombatConfig
      self.add_scope(WombatConfig.default_scope)
    if not self.is_saved():
      super(Account, self).put(*args, **kwargs)
    self.add_scope(self)
    return super(Account, self).put(*args, **kwargs)

  def refresh_tokens(self):
    if self.token:
      self.tokens
    if not self._tokens:
      self._tokens.append(str(uuid.uuid4()))
    for i in xrange(len(self._tokens)):
      if self.tokens[i] == '+':
        self._tokens[i] = self._tokens[i].replace('+', str(uuid.uuid4()))
      if not self._tokens[i]:
        self._tokens[i] = str(uuid.uuid4())

  @property
  def status_token(self):
    if not self._status_token:
      self.refresh_status_token()
    return self._status_token

  def refresh_status_token(self):
    self._status_token = str(uuid.uuid4())
    self.put()

  def validate_status_token(self, status_token):
    return self.status_token == status_token

  @property
  def status_auth_key(self):
    return '%s:%s' % (self.key(), self.status_token)

  @staticmethod
  def parse_status_auth_key(auth_key):
    auth_key = str(auth_key)
    if ':' in auth_key:
      return auth_key.split(':')
    return auth_key, None

  @decorator.memoize
  def is_default(self):
    from config import WombatConfig
    return self.key() == WombatConfig.default_scope.key()

  def get_entities_to_hide(self):
    from users.businesses.models import BusinessLocation, Company, PosDevice, ReceiptEmailLayout, ReceiptLayout, ReceiptTranslation
    from users.businesses.advertising.models import AdvertisingCampaign
    models = [Account, AdvertisingCampaign, BusinessLocation, Company, PosDevice, ReceiptEmailLayout, ReceiptLayout, ReceiptTranslation, User]
    entities_to_hide = []
    for model in models:
      for entity in model.all().scope(self):
        if entity.is_hidden() != self.is_hidden() and entity.get_scope(True, True) == [self.key()]:
          entities_to_hide.append(entity)
    return entities_to_hide

  def is_in_use(self):
    for class_obj in Wombat.get_descendant_classes():
      key = class_obj.all(keys_only=True).scope(self).filter('hidden =', False).get()
      if key and key != self.key():
        return True
      key = class_obj.all(keys_only=True).scope(self).filter('hidden =', None).get()
      if key and key != self.key():
        return True
    return False


class PasswordProperty(db.StringProperty):
  pass


class User(Wombat):
  unique_properties = ['email']
  strong_exclude = [
    'scope',
    'hashed_password',
    'last_used_account',
    'auth_code',
    'auth_code_generated_on',
  ]
  exclude = ['_account', 'type', 'time_zone']
  FIELDS_ORDER = [
    (
      'Details',
      [
        'first_name',
        'email',
        'password',
        'confirm_password',
        'time_zone',
        '_account',
        'type',
      ]
    ),
    (
      'State (read only)',
      [
        'uses_autogenerated_password',
        'last_login',
      ]
    ),
  ]
  FIELDS_VISIBILITY = {
    'guest': ['email', 'password'],
    'user': ['email', 'password', 'time_zone', 'first_name'],
    'manager': ['email', 'password', 'time_zone', 'first_name'],
    'datahub': ['first_name', 'password'],
  }

  email = db.EmailProperty(required=True)
  password = PasswordProperty(indexed=False)
  first_name = db.StringProperty(indexed=False)
  hashed_password = db.StringProperty(indexed=False)
  _account = db.ReferenceProperty(Account, required=Requirer())
  last_used_account = db.ReferenceProperty(Account, collection_name='user_preference')
  type = db.StringProperty(choices=['user', 'manager', 'admin', 'datahub', 'datahub-write-only', 'datahub-till'], default='user')
  uses_autogenerated_password = db.BooleanProperty(default=False, indexed=False)
  time_zone = db.StringProperty(default='Europe/London', choices=pytz.common_timezones)
  auth_code = db.IntegerProperty()
  auth_code_generated_on = db.DateTimeProperty()
  last_login = db.DateTimeProperty()

  LIST_PROJECTION = ['_account', 'email', 'type']
  ORDER = ['_account', 'type', 'email']

  @property
  def header(self):
    return self._account and str(self._account) or 'Unassigned'

  def __str__(self):
    return '[%s] %s' % (self.type, super(User, self).__str__())

  def refresh_scope(self, force=False):
    return

  def set_account(self, account):
    if self._account and self._account.key() in self.get_scope():
      self.remove_scope(self._account)
    self._account = account
    if account:
      self.add_scope(account)

  def get_account(self):
    return self._account

  account = property(get_account, set_account)

  def get_active_account(self):
    return self.account

  def log_in(self):
    session = self.session
    session['user_key'] = str(self.key())

  def log_out(self):
    session = self.session
    if 'user_key' in session:
      del session['user_key']

  def has_access(self, area):
    if (self.type == 'datahub-write-only' and
        area not in ['datahub-write', 'datahub']):
      return False
    if (self.type == 'datahub-till' and
        area in ['datahub-search']):
      return False
    if self.type.startswith('datahub') and not area.startswith('datahub'):
      return False
    return True


  def put(self, *args, **kwargs):
    self.set_hashed_password()
    if self.is_saved():
      current = db.get(self.key())
      if current._account and current._account != self._account and self.has_scope(current._account):
        self.remove_scope(current._account)
    if self._account:
      self.add_scope(self._account)
    self.password = ''
    return super(User, self).put(*args, **kwargs)

  def is_on_default_account(self):
    from config import WombatConfig
    return self.account and self.account.key() == WombatConfig.default_scope.key()

  @staticmethod
  def hash_password(password, salt = None):
    if not salt:
      salt = str(uuid.uuid4())
    return hashlib.sha512(password+salt).hexdigest()+'$'+salt

  def generate_password(self):
    return self.hash_password(self.password)

  def check_password(self, password):
    self.set_hashed_password()
    if not self.hashed_password:
      return False
    return self.hashed_password == self.hash_password(password, self.hashed_password.split('$')[1])

  def set_hashed_password(self):
    if self.password:
      self.hashed_password = self.generate_password()
      self.uses_autogenerated_password = False

  def get_local_time(self, umt_time):
    tz = timezone(self.time_zone)
    l_datetime = tz.normalize(utc.localize(umt_time).astimezone(tz))
    return l_datetime

  def get_auth_code(self):
    random.seed(os.urandom(512))
    self.auth_code_generated_on = datetime.datetime.now()
    self.auth_code = random.randint(111111, 999999)
    self.put()
    return self.auth_code

  def is_auth_code_valid(self, code):
    if not self.auth_code or not self.auth_code_generated_on:
      return False
    delta = datetime.datetime.now() - self.auth_code_generated_on
    if not code or delta > datetime.timedelta(minutes=10):
      return False
    return int(code) == self.auth_code

  def get_auth_token(self, code, long_term=False):
    if not self.is_auth_code_valid(code):
      return False
    key_name = str(hashlib.sha1(str(self.key())).hexdigest()) +\
               str(uuid.uuid4()).replace('-', '')
    auth_token = AuthToken(key_name=key_name, user=self, long_term=long_term)
    auth_token.put()
    self.auth_code = None
    self.put()
    return auth_token

  def is_auth_token_valid(self, token_str):
    token = AuthToken.get_by_key_name(token_str)
    if not token or token.is_hidden():
      return False
    delta = datetime.datetime.now() - token.added_on
    if not token.long_term and delta < datetime.timedelta(days=1):
      return True
    elif token.long_term and delta < datetime.timedelta(days=30):
      return True
    else:
      token.delete()
    return False

  def get_auth_token_cookie_name(self):
    return str(hashlib.sha1(str(self.key())).hexdigest())


class AuthToken(Wombat):
  user = db.ReferenceProperty(User)
  long_term = db.BooleanProperty(default=False)

  def __str__(self):
    return str(self.key().name())


class EmailMessage(db.polymodel.PolyModel, ydb.Model):
  added_on = db.DateTimeProperty(auto_now_add=True)
  modified_on = db.DateTimeProperty(auto_now=True)
  sender = db.StringProperty(indexed=False)
  reply_to = db.StringProperty(indexed=False)
  to = db.StringListProperty()
  cc = db.StringListProperty(indexed=False)
  bcc = db.StringListProperty(indexed=False)
  subject = db.StringProperty(indexed=False)
  soft_bounce_count = db.IntegerProperty(default=0)
  last_soft_bounce = db.DateTimeProperty()
  soft_bounce_hooks = db.StringListProperty(indexed=False)
  hard_bounce_hooks = db.StringListProperty(indexed=False)

  @property
  def bounces(self):
    return self.emailmessagebounce_set

  @property
  def unix_timestamp(self):
    return time.mktime(self.added_on.utctimetuple())

  @property
  def status(self):
    if self.bounces.count() > 0:
      return 'hard'
    if self.soft_bounce_count > 0:
      return 'soft'
    return 'ok'


class AmazonSESEmailMessage(EmailMessage):
  message_id = db.StringProperty()
  request_id = db.StringProperty()
  return_path = db.StringProperty(indexed=False)


class EmailMessageBounce(db.polymodel.PolyModel, ydb.Model):
  added_on = db.DateTimeProperty(auto_now_add=True)
  email_message = db.ReferenceProperty(EmailMessage)
  recipient_type = db.StringProperty(choices=['to', 'cc', 'bcc'])


class AmazonSESEmailMessageBounce(EmailMessageBounce):
  type_ = db.StringProperty(indexed=False)
  subtype = db.StringProperty(indexed=False)
  email = db.EmailProperty(indexed=False)
  action = db.StringProperty(indexed=False)
  diagnostic_code = db.TextProperty()
  status = db.StringProperty(indexed=False)
  feedback_id = db.StringProperty(indexed=False)
  reporting_MTA = db.StringProperty(indexed=False)
  timestamp_str = db.StringProperty(indexed=False)


class MockWombatActionPlugin(object):
  get_response = 'Mock get'
  post_response = 'Mock post'
  def __init__(self, request, response, entity, request_handler):
    self.response = response
  def get(self):
    self.response.out.write(self.get_response)
  def post(self):
    self.response.out.write(self.post_response)

class MockWombatModel(Wombat):
  has_default_scope_only = db.BooleanProperty(default=False)
  def __init__(self, *args, **kwargs):
    super(self.__class__, self).__init__(*args, **kwargs)
    self.register_action(MockWombatActionPlugin, 'test', 'stet')


class AccountForm(Form):
  name = fields.TextField('Company Name', validators=[validators.Length(1, message='Company Name cannot be empty.')])
  store_name = fields.TextField(validators=[validators.Length(1, message='Store name cannot be empty.')])
  store_email = fields.TextField(validators=[validators.Email(message='Store email was invalid.')])
  store_address_line_one = fields.TextField(validators=[validators.Length(1, message='Store Addresss Line 1 cannot be empty.')])
  store_address_line_two = fields.TextField(validators=[validators.Length(1, message='Store Addresss Line 2 cannot be empty.')])
  device_description = fields.TextField('Till Name', validators=[validators.Length(1, message='Till Name cannot be empty.')])


class PropertyRequirerTestModel(Wombat):
  p1 = db.StringProperty()
  p2 = db.StringProperty(required=PropertyRequirer('p1'))


class NoValuePropertyRequirerTestModel(Wombat):
  p1 = db.StringProperty()
  p2 = db.StringProperty(required=NoValuePropertyRequirer('p1'))
